<!doctype html>

<html>
<head>
  <link rel="shortcut icon" href="static/images/favicon.ico" type="image/x-icon">
  <title>enterhandler.js (Closure Library API Documentation - JavaScript)</title>
  <link rel="stylesheet" href="static/css/base.css">
  <link rel="stylesheet" href="static/css/doc.css">
  <link rel="stylesheet" href="static/css/sidetree.css">
  <link rel="stylesheet" href="static/css/prettify.css">

  <script>
     var _staticFilePath = "static/";
  </script>

  <script src="static/js/doc.js">
  </script>

  <meta charset="utf8">
</head>

<body onload="prettyPrint()">

<div id="header">
  <div class="g-section g-tpl-50-50 g-split">
    <div class="g-unit g-first">
      <a id="logo" href="index.html">Closure Library API Documentation</a>
    </div>

    <div class="g-unit">
      <div class="g-c">
        <strong>Go to class or file:</strong>
        <input type="text" id="ac">
      </div>
    </div>
  </div>
</div>

<div class="clear"></div>

<h2><a href="closure_goog_editor_plugins_enterhandler.js.html">enterhandler.js</a></h2>

<pre class="prettyprint lang-js">
<a name="line1"></a>// Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
<a name="line2"></a>// you may not use this file except in compliance with the License.
<a name="line3"></a>// You may obtain a copy of the License at
<a name="line4"></a>//
<a name="line5"></a>//     http://www.apache.org/licenses/LICENSE-2.0
<a name="line6"></a>//
<a name="line7"></a>// Unless required by applicable law or agreed to in writing, software
<a name="line8"></a>// distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
<a name="line9"></a>// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
<a name="line10"></a>// See the License for the specific language governing permissions and
<a name="line11"></a>// limitations under the License.
<a name="line12"></a>
<a name="line13"></a>// Copyright 2008 Google, Inc. All Rights Reserved.
<a name="line14"></a>
<a name="line15"></a>/**
<a name="line16"></a> * @fileoverview Plugin to handle enter keys.
<a name="line17"></a> *
<a name="line18"></a> */
<a name="line19"></a>
<a name="line20"></a>goog.provide(&#39;goog.editor.plugins.EnterHandler&#39;);
<a name="line21"></a>
<a name="line22"></a>goog.require(&#39;goog.dom&#39;);
<a name="line23"></a>goog.require(&#39;goog.dom.AbstractRange&#39;);
<a name="line24"></a>goog.require(&#39;goog.dom.NodeOffset&#39;);
<a name="line25"></a>goog.require(&#39;goog.dom.NodeType&#39;);
<a name="line26"></a>goog.require(&#39;goog.dom.TagName&#39;);
<a name="line27"></a>goog.require(&#39;goog.dom.classes&#39;);
<a name="line28"></a>goog.require(&#39;goog.editor.BrowserFeature&#39;);
<a name="line29"></a>goog.require(&#39;goog.editor.Plugin&#39;);
<a name="line30"></a>goog.require(&#39;goog.editor.node&#39;);
<a name="line31"></a>goog.require(&#39;goog.editor.plugins.Blockquote&#39;);
<a name="line32"></a>goog.require(&#39;goog.editor.range&#39;);
<a name="line33"></a>goog.require(&#39;goog.editor.style&#39;);
<a name="line34"></a>goog.require(&#39;goog.events.KeyCodes&#39;);
<a name="line35"></a>goog.require(&#39;goog.string&#39;);
<a name="line36"></a>goog.require(&#39;goog.userAgent&#39;);
<a name="line37"></a>
<a name="line38"></a>
<a name="line39"></a>/**
<a name="line40"></a> * Plugin to handle enter keys. This does all the crazy to normalize (as much as
<a name="line41"></a> * is reasonable) what happens when you hit enter. This also handles the
<a name="line42"></a> * special casing of hitting enter in a blockquote.
<a name="line43"></a> *
<a name="line44"></a> * In IE and Safari, the resulting HTML uses one DIV tag per line.  In FireFox,
<a name="line45"></a> * the resulting HTML uses BR tags at the end of each line.
<a name="line46"></a> *
<a name="line47"></a> * @constructor
<a name="line48"></a> * @extends {goog.editor.Plugin}
<a name="line49"></a> */
<a name="line50"></a>goog.editor.plugins.EnterHandler = function() {
<a name="line51"></a>  goog.editor.Plugin.call(this);
<a name="line52"></a>};
<a name="line53"></a>goog.inherits(goog.editor.plugins.EnterHandler, goog.editor.Plugin);
<a name="line54"></a>
<a name="line55"></a>
<a name="line56"></a>/** @inheritDoc */
<a name="line57"></a>goog.editor.plugins.EnterHandler.prototype.getTrogClassId = function() {
<a name="line58"></a>  return &#39;EnterHandler&#39;;
<a name="line59"></a>};
<a name="line60"></a>
<a name="line61"></a>
<a name="line62"></a>/**
<a name="line63"></a> * If the contents are empty, return the &#39;default&#39; html for the field.
<a name="line64"></a> * The &#39;default&#39; contents depend on the enter handling mode, so it
<a name="line65"></a> * makes the most sense in this plugin.
<a name="line66"></a> * @param {string} html The html to prepare.
<a name="line67"></a> * @return {string} The original HTML, or default contents if that
<a name="line68"></a> *    html is empty.
<a name="line69"></a> */
<a name="line70"></a>goog.editor.plugins.EnterHandler.prototype.prepareContentsHtml = function(
<a name="line71"></a>    html) {
<a name="line72"></a>  if (!html || goog.string.isBreakingWhitespace(html)) {
<a name="line73"></a>    return goog.editor.BrowserFeature.COLLAPSES_EMPTY_NODES ?
<a name="line74"></a>        this.getNonCollapsingBlankHtml() : &#39;&#39;;
<a name="line75"></a>  }
<a name="line76"></a>  return html;
<a name="line77"></a>};
<a name="line78"></a>
<a name="line79"></a>
<a name="line80"></a>/**
<a name="line81"></a> * Gets HTML with no contents that won&#39;t collapse, for browsers that
<a name="line82"></a> * collapse the empty string.
<a name="line83"></a> * @return {string} Blank html.
<a name="line84"></a> * @protected
<a name="line85"></a> */
<a name="line86"></a>goog.editor.plugins.EnterHandler.prototype.getNonCollapsingBlankHtml =
<a name="line87"></a>    goog.functions.constant(&#39;&lt;br&gt;&#39;);
<a name="line88"></a>
<a name="line89"></a>
<a name="line90"></a>/**
<a name="line91"></a> * Internal backspace handler.
<a name="line92"></a> * @param {goog.events.Event} e The keypress event.
<a name="line93"></a> * @param {goog.dom.AbstractRange} range The closure range object.
<a name="line94"></a> * @protected
<a name="line95"></a> */
<a name="line96"></a>goog.editor.plugins.EnterHandler.prototype.handleBackspaceInternal = function(e,
<a name="line97"></a>    range) {
<a name="line98"></a>  var field = this.fieldObject.getElement();
<a name="line99"></a>  var container = range &amp;&amp; range.getStartNode();
<a name="line100"></a>
<a name="line101"></a>  if (field.firstChild == container &amp;&amp; goog.editor.node.isEmpty(container)) {
<a name="line102"></a>    e.preventDefault();
<a name="line103"></a>    // TODO: I think we probably don&#39;t need to stopPropagation here
<a name="line104"></a>    e.stopPropagation();
<a name="line105"></a>  }
<a name="line106"></a>};
<a name="line107"></a>
<a name="line108"></a>
<a name="line109"></a>/**
<a name="line110"></a> * Fix paragraphs to be the correct type of node.
<a name="line111"></a> * @param {goog.events.Event} e The &lt;enter&gt; key event.
<a name="line112"></a> * @param {boolean} split Whether we already split up a blockquote by
<a name="line113"></a> *     manually inserting elements.
<a name="line114"></a> * @protected
<a name="line115"></a> */
<a name="line116"></a>goog.editor.plugins.EnterHandler.prototype.processParagraphTagsInternal =
<a name="line117"></a>    function(e, split) {
<a name="line118"></a>  // Force IE to turn the node we are leaving into a DIV.  If we do turn
<a name="line119"></a>  // it into a DIV, the node IE creates in response to ENTER will also be
<a name="line120"></a>  // a DIV.  If we don&#39;t, it will be a P.  We handle that case
<a name="line121"></a>  // in handleKeyUpIE_
<a name="line122"></a>  if (goog.userAgent.IE || goog.userAgent.OPERA) {
<a name="line123"></a>    this.ensureBlockIeOpera(goog.dom.TagName.DIV);
<a name="line124"></a>  } else if (!split &amp;&amp; goog.userAgent.WEBKIT) {
<a name="line125"></a>    // WebKit duplicates a blockquote when the user hits enter. Let&#39;s cancel
<a name="line126"></a>    // this and insert a BR instead, to make it more consistent with the other
<a name="line127"></a>    // browsers.
<a name="line128"></a>    var range = this.fieldObject.getRange();
<a name="line129"></a>    if (!range || !goog.editor.plugins.EnterHandler.isDirectlyInBlockquote(
<a name="line130"></a>        range.getContainerElement())) {
<a name="line131"></a>      return;
<a name="line132"></a>    }
<a name="line133"></a>
<a name="line134"></a>    var dh = this.getFieldDomHelper();
<a name="line135"></a>    var br = dh.createElement(goog.dom.TagName.BR);
<a name="line136"></a>    range.insertNode(br, true);
<a name="line137"></a>
<a name="line138"></a>    // If the BR is at the end of a block element, Safari still thinks there is
<a name="line139"></a>    // only one line instead of two, so we need to add another BR in that case.
<a name="line140"></a>    if (goog.editor.node.isBlockTag(br.parentNode) &amp;&amp;
<a name="line141"></a>        !goog.editor.node.skipEmptyTextNodes(br.nextSibling)) {
<a name="line142"></a>      goog.dom.insertSiblingBefore(
<a name="line143"></a>          dh.createElement(goog.dom.TagName.BR), br);
<a name="line144"></a>    }
<a name="line145"></a>
<a name="line146"></a>    goog.editor.range.placeCursorNextTo(br, false);
<a name="line147"></a>    e.preventDefault();
<a name="line148"></a>  }
<a name="line149"></a>};
<a name="line150"></a>
<a name="line151"></a>
<a name="line152"></a>/**
<a name="line153"></a> * Determines whether the lowest containing block node is a blockquote.
<a name="line154"></a> * @param {Node} n The node.
<a name="line155"></a> * @return {boolean} Whether the deepest block ancestor of n is a blockquote.
<a name="line156"></a> */
<a name="line157"></a>goog.editor.plugins.EnterHandler.isDirectlyInBlockquote = function(n) {
<a name="line158"></a>  for (var current = n; current; current = current.parentNode) {
<a name="line159"></a>    if (goog.editor.node.isBlockTag(current)) {
<a name="line160"></a>      return current.tagName == goog.dom.TagName.BLOCKQUOTE;
<a name="line161"></a>    }
<a name="line162"></a>  }
<a name="line163"></a>
<a name="line164"></a>  return false;
<a name="line165"></a>};
<a name="line166"></a>
<a name="line167"></a>
<a name="line168"></a>/**
<a name="line169"></a> * Internal delete key handler.
<a name="line170"></a> * @param {goog.events.Event} e The keypress event.
<a name="line171"></a> * @protected
<a name="line172"></a> */
<a name="line173"></a>goog.editor.plugins.EnterHandler.prototype.handleDeleteGecko = function(e) {
<a name="line174"></a>  this.deleteBrGecko(e);
<a name="line175"></a>};
<a name="line176"></a>
<a name="line177"></a>
<a name="line178"></a>/**
<a name="line179"></a> * Deletes the element at the cursor if it is a BR node, and if it does, calls
<a name="line180"></a> * e.preventDefault to stop the browser from deleting. Only necessary in Gecko
<a name="line181"></a> * as a workaround for mozilla bug 205350 where deleting a BR that is followed
<a name="line182"></a> * by a block element doesn&#39;t work (the BR gets immediately replaced). We also
<a name="line183"></a> * need to account for an ill-formed cursor which occurs from us trying to
<a name="line184"></a> * stop the browser from deleting.
<a name="line185"></a> *
<a name="line186"></a> * @param {goog.events.Event} e The DELETE keypress event.
<a name="line187"></a> * @protected
<a name="line188"></a> */
<a name="line189"></a>goog.editor.plugins.EnterHandler.prototype.deleteBrGecko = function(e) {
<a name="line190"></a>  var range = this.fieldObject.getRange();
<a name="line191"></a>  if (range.isCollapsed()) {
<a name="line192"></a>    var container = range.getEndNode();
<a name="line193"></a>    if (container.nodeType == goog.dom.NodeType.ELEMENT) {
<a name="line194"></a>      var nextNode = container.childNodes[range.getEndOffset()];
<a name="line195"></a>      if (nextNode &amp;&amp; nextNode.tagName == goog.dom.TagName.BR) {
<a name="line196"></a>        // We want to retrieve the first non-whitespace previous sibling
<a name="line197"></a>        // as we could have added an empty text node below and want to
<a name="line198"></a>        // properly handle deleting a sequence of BR&#39;s.
<a name="line199"></a>        var previousSibling = goog.editor.node.getPreviousSibling(nextNode);
<a name="line200"></a>        var nextSibling = nextNode.nextSibling;
<a name="line201"></a>
<a name="line202"></a>        container.removeChild(nextNode);
<a name="line203"></a>        e.preventDefault();
<a name="line204"></a>
<a name="line205"></a>        // When we delete a BR followed by a block level element, the cursor
<a name="line206"></a>        // has a line-height which spans the height of the block level element.
<a name="line207"></a>        // e.g. If we delete a BR followed by a UL, the resulting HTML will
<a name="line208"></a>        // appear to the end user like:-
<a name="line209"></a>        //
<a name="line210"></a>        // |  * one
<a name="line211"></a>        // |  * two
<a name="line212"></a>        // |  * three
<a name="line213"></a>        //
<a name="line214"></a>        // There are a couple of cases that we have to account for in order to
<a name="line215"></a>        // properly conform to what the user expects when DELETE is pressed.
<a name="line216"></a>        //
<a name="line217"></a>        // 1. If the BR has a previous sibling and the previous sibling is
<a name="line218"></a>        //    not a block level element or a BR, we place the cursor at the
<a name="line219"></a>        //    end of that.
<a name="line220"></a>        // 2. If the BR doesn&#39;t have a previous sibling or the previous sibling
<a name="line221"></a>        //    is a block level element or a BR, we place the cursor at the
<a name="line222"></a>        //    beginning of the leftmost leaf of its next sibling.
<a name="line223"></a>        if (nextSibling &amp;&amp; goog.editor.node.isBlockTag(nextSibling)) {
<a name="line224"></a>          if (previousSibling &amp;&amp;
<a name="line225"></a>              !(previousSibling.tagName == goog.dom.TagName.BR ||
<a name="line226"></a>                goog.editor.node.isBlockTag(previousSibling))) {
<a name="line227"></a>            goog.dom.Range.createCaret(
<a name="line228"></a>                previousSibling,
<a name="line229"></a>                goog.editor.node.getLength(previousSibling)).select();
<a name="line230"></a>          } else {
<a name="line231"></a>            var leftMostLeaf = goog.editor.node.getLeftMostLeaf(nextSibling);
<a name="line232"></a>            goog.dom.Range.createCaret(leftMostLeaf, 0).select();
<a name="line233"></a>          }
<a name="line234"></a>        }
<a name="line235"></a>      }
<a name="line236"></a>    }
<a name="line237"></a>  }
<a name="line238"></a>};
<a name="line239"></a>
<a name="line240"></a>
<a name="line241"></a>/**
<a name="line242"></a> * Handle keypress events.
<a name="line243"></a> * @param {goog.events.BrowserEvent} e The key event.
<a name="line244"></a> */
<a name="line245"></a>goog.editor.plugins.EnterHandler.prototype.handleKeyPress = function(e) {
<a name="line246"></a>  // If a dialog doesn&#39;t have selectable field, Gecko grabs the event and
<a name="line247"></a>  // performs actions in editor window. This solves that problem and allows
<a name="line248"></a>  // the event to be passed on to proper handlers.
<a name="line249"></a>  if (goog.userAgent.GECKO &amp;&amp; this.fieldObject.inModalMode()) {
<a name="line250"></a>    return;
<a name="line251"></a>  }
<a name="line252"></a>
<a name="line253"></a>  // Firefox will allow the first node in an iframe to be deleted
<a name="line254"></a>  // on a backspace.  Disallow it if the node is empty.
<a name="line255"></a>  if (e.keyCode == goog.events.KeyCodes.BACKSPACE) {
<a name="line256"></a>    this.handleBackspaceInternal(e, this.fieldObject.getRange());
<a name="line257"></a>
<a name="line258"></a>  } else if (e.keyCode == goog.events.KeyCodes.ENTER) {
<a name="line259"></a>    if (goog.userAgent.GECKO) {
<a name="line260"></a>      if (!e.shiftKey) {
<a name="line261"></a>        // Behave similarly to IE&#39;s content editable return carriage:
<a name="line262"></a>        // If the shift key is down or specified by the application, insert a
<a name="line263"></a>        // BR, otherwise split paragraphs
<a name="line264"></a>        this.handleEnterGecko_(e);
<a name="line265"></a>      }
<a name="line266"></a>    } else {
<a name="line267"></a>      // In Gecko-based browsers, this is handled in the handleEnterGecko_
<a name="line268"></a>      // method.
<a name="line269"></a>      this.fieldObject.dispatchBeforeChange();
<a name="line270"></a>      var cursorPosition = this.deleteCursorSelection_();
<a name="line271"></a>
<a name="line272"></a>      var split = !!this.fieldObject.execCommand(
<a name="line273"></a>          goog.editor.plugins.Blockquote.SPLIT_COMMAND, cursorPosition);
<a name="line274"></a>      if (split) {
<a name="line275"></a>        // TODO: I think we probably don&#39;t need to stopPropagation here
<a name="line276"></a>        e.preventDefault();
<a name="line277"></a>        e.stopPropagation();
<a name="line278"></a>      }
<a name="line279"></a>
<a name="line280"></a>      this.releasePositionObject_(cursorPosition);
<a name="line281"></a>
<a name="line282"></a>      if (goog.userAgent.WEBKIT) {
<a name="line283"></a>        this.handleEnterWebkitInternal(e);
<a name="line284"></a>      }
<a name="line285"></a>
<a name="line286"></a>      this.processParagraphTagsInternal(e, split);
<a name="line287"></a>      this.fieldObject.dispatchChange();
<a name="line288"></a>    }
<a name="line289"></a>
<a name="line290"></a>  } else if (goog.userAgent.GECKO &amp;&amp; e.keyCode == goog.events.KeyCodes.DELETE) {
<a name="line291"></a>    this.handleDeleteGecko(e);
<a name="line292"></a>  }
<a name="line293"></a>};
<a name="line294"></a>
<a name="line295"></a>
<a name="line296"></a>/**
<a name="line297"></a> * Handle keyup events.
<a name="line298"></a> * @param {goog.events.Event} e The key event.
<a name="line299"></a> */
<a name="line300"></a>goog.editor.plugins.EnterHandler.prototype.handleKeyUp = function(e) {
<a name="line301"></a>  // If a dialog doesn&#39;t have selectable field, Gecko grabs the event and
<a name="line302"></a>  // performs actions in editor window. This solves that problem and allows
<a name="line303"></a>  // the event to be passed on to proper handlers.
<a name="line304"></a>  if (goog.userAgent.GECKO &amp;&amp; this.fieldObject.inModalMode()) {
<a name="line305"></a>    return;
<a name="line306"></a>  }
<a name="line307"></a>  this.handleKeyUpInternal(e);
<a name="line308"></a>};
<a name="line309"></a>
<a name="line310"></a>
<a name="line311"></a>/**
<a name="line312"></a> * Internal handler for keyup events.
<a name="line313"></a> * @param {goog.events.Event} e The key event.
<a name="line314"></a> * @protected
<a name="line315"></a> */
<a name="line316"></a>goog.editor.plugins.EnterHandler.prototype.handleKeyUpInternal = function(e) {
<a name="line317"></a>  if ((goog.userAgent.IE || goog.userAgent.OPERA) &amp;&amp;
<a name="line318"></a>      e.keyCode == goog.events.KeyCodes.ENTER) {
<a name="line319"></a>    this.ensureBlockIeOpera(goog.dom.TagName.DIV, true);
<a name="line320"></a>  }
<a name="line321"></a>};
<a name="line322"></a>
<a name="line323"></a>
<a name="line324"></a>/**
<a name="line325"></a> * Handles an enter keypress event on fields in Gecko.
<a name="line326"></a> * @param {goog.events.BrowserEvent} e The key event.
<a name="line327"></a> * @private
<a name="line328"></a> */
<a name="line329"></a>goog.editor.plugins.EnterHandler.prototype.handleEnterGecko_ = function(e) {
<a name="line330"></a>  // Retrieve whether the selection is collapsed before we delete it.
<a name="line331"></a>  var range = this.fieldObject.getRange();
<a name="line332"></a>  var wasCollapsed = !range || range.isCollapsed();
<a name="line333"></a>  var cursorPosition = this.deleteCursorSelection_();
<a name="line334"></a>
<a name="line335"></a>  var handled = this.fieldObject.execCommand(
<a name="line336"></a>      goog.editor.plugins.Blockquote.SPLIT_COMMAND, cursorPosition);
<a name="line337"></a>  if (handled) {
<a name="line338"></a>    // TODO: I think we probably don&#39;t need to stopPropagation here
<a name="line339"></a>    e.preventDefault();
<a name="line340"></a>    e.stopPropagation();
<a name="line341"></a>  }
<a name="line342"></a>
<a name="line343"></a>  this.releasePositionObject_(cursorPosition);
<a name="line344"></a>  if (!handled) {
<a name="line345"></a>    this.handleEnterAtCursorGeckoInternal(e, wasCollapsed, range);
<a name="line346"></a>  }
<a name="line347"></a>};
<a name="line348"></a>
<a name="line349"></a>
<a name="line350"></a>/**
<a name="line351"></a> * Handle an enter key press in WebKit.
<a name="line352"></a> * @param {goog.events.BrowserEvent} e The key press event.
<a name="line353"></a> * @protected
<a name="line354"></a> */
<a name="line355"></a>goog.editor.plugins.EnterHandler.prototype.handleEnterWebkitInternal =
<a name="line356"></a>    goog.nullFunction;
<a name="line357"></a>
<a name="line358"></a>
<a name="line359"></a>/**
<a name="line360"></a> * Handle an enter key press on collapsed selection.  handleEnterGecko_ ensures
<a name="line361"></a> * the selection is collapsed by deleting its contents if it is not.  The
<a name="line362"></a> * default implementation does nothing.
<a name="line363"></a> * @param {goog.events.BrowserEvent} e The key press event.
<a name="line364"></a> * @param {boolean} wasCollapsed Whether the selection was collapsed before
<a name="line365"></a> *     the key press.  If it was not, code before this function has already
<a name="line366"></a> *     cleared the contents of the selection.
<a name="line367"></a> * @param {goog.dom.AbstractRange} range Object representing the selection.
<a name="line368"></a> * @protected
<a name="line369"></a> */
<a name="line370"></a>goog.editor.plugins.EnterHandler.prototype.handleEnterAtCursorGeckoInternal =
<a name="line371"></a>    goog.nullFunction;
<a name="line372"></a>
<a name="line373"></a>
<a name="line374"></a>/**
<a name="line375"></a> * Names of all the nodes that we don&#39;t want to turn into block nodes in IE when
<a name="line376"></a> * the user hits enter.
<a name="line377"></a> * @type {Object}
<a name="line378"></a> * @private
<a name="line379"></a> */
<a name="line380"></a>goog.editor.plugins.EnterHandler.DO_NOT_ENSURE_BLOCK_NODES_ =
<a name="line381"></a>    goog.object.createSet(
<a name="line382"></a>        goog.dom.TagName.LI, goog.dom.TagName.DIV, goog.dom.TagName.H1,
<a name="line383"></a>        goog.dom.TagName.H2, goog.dom.TagName.H3, goog.dom.TagName.H4,
<a name="line384"></a>        goog.dom.TagName.H5, goog.dom.TagName.H6);
<a name="line385"></a>
<a name="line386"></a>
<a name="line387"></a>/**
<a name="line388"></a> * Whether this is a node that contains a single BR tag and non-nbsp
<a name="line389"></a> * whitespace.
<a name="line390"></a> * @param {Element} node Node to check.
<a name="line391"></a> * @return {boolean} Whether this is an element that only contains a BR.
<a name="line392"></a> * @protected
<a name="line393"></a> */
<a name="line394"></a>goog.editor.plugins.EnterHandler.isBrElem = function(node) {
<a name="line395"></a>  return goog.editor.node.isEmpty(node) &amp;&amp;
<a name="line396"></a>      node.getElementsByTagName(goog.dom.TagName.BR).length == 1;
<a name="line397"></a>};
<a name="line398"></a>
<a name="line399"></a>
<a name="line400"></a>/**
<a name="line401"></a> * Ensures all text in IE and Opera to be in the given tag in order to control
<a name="line402"></a> * Enter spacing. Call this when Enter is pressed if desired.
<a name="line403"></a> *
<a name="line404"></a> * We want to make sure the user is always inside of a block (or other nodes
<a name="line405"></a> * listed in goog.editor.plugins.EnterHandler.IGNORE_ENSURE_BLOCK_NODES_).  We
<a name="line406"></a> * listen to keypress to force nodes that the user is leaving to turn into
<a name="line407"></a> * blocks, but we also need to listen to keyup to force nodes that the user is
<a name="line408"></a> * entering to turn into blocks.
<a name="line409"></a> * Example:  html is: &quot;&lt;h2&gt;foo[cursor]&lt;/h2&gt;&quot;, and the user hits enter.  We
<a name="line410"></a> * don&#39;t want to format the h2, but we do want to format the P that is
<a name="line411"></a> * created on enter.  The P node is not available until keyup.
<a name="line412"></a> * @param {goog.dom.TagName} tag The tag name to convert to.
<a name="line413"></a> * @param {boolean} opt_keyUp Whether the function is being called on key up.
<a name="line414"></a> *     When called on key up, the cursor is in the newly created node, so the
<a name="line415"></a> *     semantics for when to change it to a block are different.  Specifically,
<a name="line416"></a> *     if the resulting node contains only a BR, it is converted to &lt;tag&gt;.
<a name="line417"></a> * @protected
<a name="line418"></a> */
<a name="line419"></a>goog.editor.plugins.EnterHandler.prototype.ensureBlockIeOpera = function(tag,
<a name="line420"></a>    opt_keyUp) {
<a name="line421"></a>  // SelectionUtil&#39;s getParentContainer does not work here.  The problem is
<a name="line422"></a>  // that IE seems to be in the middle of adding the element to the dom, and the
<a name="line423"></a>  // current dom is inconsistent.  Specfically, the element&#39;s innerHTML,
<a name="line424"></a>  // outerHTML, and childNode&#39;s say the element is empty (innerHTML = &quot;&quot;,
<a name="line425"></a>  // childNodes.length = 0, outerHTML = &quot;&lt;DIV&lt;/DIV&gt;&quot;), but the text range and
<a name="line426"></a>  // the element&#39;s parent&#39;s innerHTML and outerHTML show an &amp;nsbp; in the
<a name="line427"></a>  // element. This leads to getParentContainer calculating the wrong container
<a name="line428"></a>  // here.
<a name="line429"></a>  // TODO: Determine the reason this causes IE to work.
<a name="line430"></a>  var container, range;
<a name="line431"></a>  if (goog.userAgent.IE) {
<a name="line432"></a>    range = this.fieldObject.getRange().getBrowserRangeObject();
<a name="line433"></a>    container = range.parentElement();
<a name="line434"></a>  } else {
<a name="line435"></a>    range = this.fieldObject.getRange();
<a name="line436"></a>    container = range.getContainer();
<a name="line437"></a>  }
<a name="line438"></a>  var field = this.fieldObject.getElement();
<a name="line439"></a>
<a name="line440"></a>  var paragraph;
<a name="line441"></a>  while (container &amp;&amp; container != field) {
<a name="line442"></a>    // We only need to ensure a block if we aren&#39;t already in the same block,
<a name="line443"></a>    // or another block level node that we don&#39;t want to change the format of.
<a name="line444"></a>    var nodeName = container.nodeName;
<a name="line445"></a>    var doNotEnsureBlock =
<a name="line446"></a>        goog.editor.plugins.EnterHandler.DO_NOT_ENSURE_BLOCK_NODES_[nodeName];
<a name="line447"></a>    var isBr = goog.editor.plugins.EnterHandler.isBrElem(container);
<a name="line448"></a>    if (nodeName == tag || (doNotEnsureBlock &amp;&amp; (!opt_keyUp || !isBr))) {
<a name="line449"></a>      // Opera can create a &lt;p&gt; inside of a &lt;div&gt; in some situations,
<a name="line450"></a>      // such as when breaking out of a list that is contained in a &lt;div&gt;.
<a name="line451"></a>      if (goog.userAgent.OPERA &amp;&amp; paragraph) {
<a name="line452"></a>        if (nodeName == tag &amp;&amp;
<a name="line453"></a>            paragraph == container.lastChild &amp;&amp;
<a name="line454"></a>            goog.editor.node.isEmpty(paragraph)) {
<a name="line455"></a>          goog.dom.insertSiblingAfter(paragraph, container);
<a name="line456"></a>          goog.dom.Range.createFromNodeContents(paragraph).select();
<a name="line457"></a>        }
<a name="line458"></a>        break;
<a name="line459"></a>      }
<a name="line460"></a>      return;
<a name="line461"></a>    }
<a name="line462"></a>    if (goog.userAgent.OPERA &amp;&amp; opt_keyUp &amp;&amp; nodeName == goog.dom.TagName.P &amp;&amp;
<a name="line463"></a>        nodeName != tag) {
<a name="line464"></a>      paragraph = container;
<a name="line465"></a>    }
<a name="line466"></a>
<a name="line467"></a>    container = container.parentNode;
<a name="line468"></a>  }
<a name="line469"></a>
<a name="line470"></a>
<a name="line471"></a>  if (goog.userAgent.IE) {
<a name="line472"></a>    // IE has a bug where if the cursor is directly before a block node
<a name="line473"></a>    // (e.g., the content is &quot;foo[cursor]&lt;blockquote&gt;bar&lt;/blockquote&gt;&quot;),
<a name="line474"></a>    // the FormatBlock command actually formats the &quot;bar&quot; instead of the &quot;foo&quot;.
<a name="line475"></a>    // This is just wrong. To work-around this, we want to move the
<a name="line476"></a>    // selection back one character, and then restore it to its prior position.
<a name="line477"></a>    // NOTE: We use the following &quot;range math&quot; to detect this situation because
<a name="line478"></a>    // using Closure ranges here triggers a bug in IE that causes a crash.
<a name="line479"></a>    // parent2 != parent3 ensures moving the cursor forward one character
<a name="line480"></a>    // crosses at least 1 element boundary, and therefore tests if the cursor is
<a name="line481"></a>    // at such a boundary.  The second check, parent3 != range.parentElement()
<a name="line482"></a>    // weeds out some cases where the elements are siblings instead of cousins.
<a name="line483"></a>    var needsHelp = false;
<a name="line484"></a>    var range2 = range.duplicate();
<a name="line485"></a>    range2.moveEnd(&#39;character&#39;, 1);
<a name="line486"></a>    // In whitebox mode, when the cursor is at the end of the field, trying to
<a name="line487"></a>    // move the end of the range will do nothing, and hence the range&#39;s text
<a name="line488"></a>    // will be empty.  In this case, the cursor clearly isn&#39;t sitting just
<a name="line489"></a>    // before a block node, since it isn&#39;t before anything.
<a name="line490"></a>    if (range2.text.length) {
<a name="line491"></a>      var parent2 = range2.parentElement();
<a name="line492"></a>
<a name="line493"></a>      var range3 = range2.duplicate();
<a name="line494"></a>      range3.collapse(false);
<a name="line495"></a>      var parent3 = range3.parentElement();
<a name="line496"></a>
<a name="line497"></a>      if ((needsHelp = parent2 != parent3 &amp;&amp;
<a name="line498"></a>                       parent3 != range.parentElement())) {
<a name="line499"></a>        range.move(&#39;character&#39;, -1);
<a name="line500"></a>        range.select();
<a name="line501"></a>      }
<a name="line502"></a>    }
<a name="line503"></a>  }
<a name="line504"></a>
<a name="line505"></a>  this.fieldObject.getEditableDomHelper().getDocument().execCommand(
<a name="line506"></a>      &#39;FormatBlock&#39;, false, &#39;&lt;&#39; + tag + &#39;&gt;&#39;);
<a name="line507"></a>
<a name="line508"></a>  if (needsHelp) {
<a name="line509"></a>    range.move(&#39;character&#39;, 1);
<a name="line510"></a>    range.select();
<a name="line511"></a>  }
<a name="line512"></a>};
<a name="line513"></a>
<a name="line514"></a>
<a name="line515"></a>/**
<a name="line516"></a> * Deletes the content at the current cursor position.
<a name="line517"></a> * @return {Node|Object} Something representing the current cursor position.
<a name="line518"></a> *    See deleteCursorSelectionIE_ and deleteCursorSelectionW3C_ for details.
<a name="line519"></a> *    Should be passed to releasePositionObject_ when no longer in use.
<a name="line520"></a> * @private
<a name="line521"></a> */
<a name="line522"></a>goog.editor.plugins.EnterHandler.prototype.deleteCursorSelection_ = function() {
<a name="line523"></a>  return goog.editor.BrowserFeature.HAS_W3C_RANGES ?
<a name="line524"></a>      this.deleteCursorSelectionW3C_() : this.deleteCursorSelectionIE_();
<a name="line525"></a>};
<a name="line526"></a>
<a name="line527"></a>
<a name="line528"></a>/**
<a name="line529"></a> * Releases the object returned by deleteCursorSelection_.
<a name="line530"></a> * @param {Node|Object} position The object returned by deleteCursorSelection_.
<a name="line531"></a> * @private
<a name="line532"></a> */
<a name="line533"></a>goog.editor.plugins.EnterHandler.prototype.releasePositionObject_ =
<a name="line534"></a>    function(position) {
<a name="line535"></a>  if (!goog.editor.BrowserFeature.HAS_W3C_RANGES) {
<a name="line536"></a>    (/** @type {Node} */ (position)).removeNode(true);
<a name="line537"></a>  }
<a name="line538"></a>};
<a name="line539"></a>
<a name="line540"></a>
<a name="line541"></a>/**
<a name="line542"></a> * Delete the selection at the current cursor position, then returns a temporary
<a name="line543"></a> * node at the current position.
<a name="line544"></a> * @return {Node} A temporary node marking the current cursor position. This
<a name="line545"></a> *     node should eventually be removed from the DOM.
<a name="line546"></a> * @private
<a name="line547"></a> */
<a name="line548"></a>goog.editor.plugins.EnterHandler.prototype.deleteCursorSelectionIE_ =
<a name="line549"></a>    function() {
<a name="line550"></a>  var doc = this.getFieldDomHelper().getDocument();
<a name="line551"></a>  var range = doc.selection.createRange();
<a name="line552"></a>
<a name="line553"></a>  var id = goog.string.createUniqueString();
<a name="line554"></a>  range.pasteHTML(&#39;&lt;span id=&quot;&#39; + id + &#39;&quot;&gt;&lt;/span&gt;&#39;);
<a name="line555"></a>  var splitNode = doc.getElementById(id);
<a name="line556"></a>  splitNode.id = &#39;&#39;;
<a name="line557"></a>  return splitNode;
<a name="line558"></a>};
<a name="line559"></a>
<a name="line560"></a>
<a name="line561"></a>/**
<a name="line562"></a> * Delete the selection at the current cursor position, then returns the node
<a name="line563"></a> * at the current position.
<a name="line564"></a> * @return {goog.editor.range.Point_} The current cursor position. Note that
<a name="line565"></a> *    unlike simulateEnterIE_, this should not be removed from the DOM.
<a name="line566"></a> * @private
<a name="line567"></a> */
<a name="line568"></a>goog.editor.plugins.EnterHandler.prototype.deleteCursorSelectionW3C_ =
<a name="line569"></a>    function() {
<a name="line570"></a>  var range = this.fieldObject.getRange();
<a name="line571"></a>
<a name="line572"></a>  // Delete the current selection if it&#39;s is non-collapsed.
<a name="line573"></a>  // Although this is redundant in FF, it&#39;s necessary for Safari
<a name="line574"></a>  if (!range.isCollapsed()) {
<a name="line575"></a>    var shouldDelete = true;
<a name="line576"></a>    // Opera selects the &lt;br&gt; in an empty block if there is no text node
<a name="line577"></a>    // preceding it. To preserve inline formatting when pressing [enter] inside
<a name="line578"></a>    // an empty block, don&#39;t delete the selection if it only selects a &lt;br&gt; at
<a name="line579"></a>    // the end of the block.
<a name="line580"></a>    // TODO: Move this into goog.dom.Range. It should detect this state
<a name="line581"></a>    // when creating a range from the window selection and fix it in the created
<a name="line582"></a>    // range.
<a name="line583"></a>    if (goog.userAgent.OPERA) {
<a name="line584"></a>      var startNode = range.getStartNode();
<a name="line585"></a>      var startOffset = range.getStartOffset();
<a name="line586"></a>      if (startNode == range.getEndNode() &amp;&amp;
<a name="line587"></a>          // This weeds out cases where startNode is a text node.
<a name="line588"></a>          startNode.lastChild &amp;&amp;
<a name="line589"></a>          startNode.lastChild.tagName == goog.dom.TagName.BR &amp;&amp;
<a name="line590"></a>          // If this check is true, then endOffset is implied to be
<a name="line591"></a>          // startOffset + 1, because the selection is not collapsed and
<a name="line592"></a>          // it starts and ends within the same element.
<a name="line593"></a>          startOffset == startNode.childNodes.length - 1) {
<a name="line594"></a>        shouldDelete = false;
<a name="line595"></a>      }
<a name="line596"></a>    }
<a name="line597"></a>    if (shouldDelete) {
<a name="line598"></a>      goog.editor.plugins.EnterHandler.deleteW3cRange_(range);
<a name="line599"></a>    }
<a name="line600"></a>  }
<a name="line601"></a>
<a name="line602"></a>  return goog.editor.range.getDeepEndPoint(range, true);
<a name="line603"></a>};
<a name="line604"></a>
<a name="line605"></a>
<a name="line606"></a>/**
<a name="line607"></a> * Deletes the contents of the selection from the DOM.
<a name="line608"></a> * @param {goog.dom.AbstractRange} range The range to remove contents from.
<a name="line609"></a> * @private
<a name="line610"></a> */
<a name="line611"></a>goog.editor.plugins.EnterHandler.deleteW3cRange_ = function(range) {
<a name="line612"></a>  if (range &amp;&amp; !range.isCollapsed()) {
<a name="line613"></a>    var reselect = true;
<a name="line614"></a>    var baseNode = range.getContainerElement();
<a name="line615"></a>    var nodeOffset = new goog.dom.NodeOffset(range.getStartNode(), baseNode);
<a name="line616"></a>    var rangeOffset = range.getStartOffset();
<a name="line617"></a>
<a name="line618"></a>    // Whether the selection crosses no container boundaries.
<a name="line619"></a>    var isInOneContainer =
<a name="line620"></a>        goog.editor.plugins.EnterHandler.isInOneContainerW3c_(range);
<a name="line621"></a>
<a name="line622"></a>    // Whether the selection ends in a container it doesn&#39;t fully select.
<a name="line623"></a>    var isPartialEnd = !isInOneContainer &amp;&amp;
<a name="line624"></a>        goog.editor.plugins.EnterHandler.isPartialEndW3c_(range);
<a name="line625"></a>
<a name="line626"></a>    // Remove The range contents, and ensure the correct content stays selected.
<a name="line627"></a>    range.removeContents();
<a name="line628"></a>    range = goog.dom.Range.createCaret(nodeOffset.findTargetNode(baseNode),
<a name="line629"></a>        rangeOffset);
<a name="line630"></a>    range.select();
<a name="line631"></a>
<a name="line632"></a>    // If we just deleted everything from the container, add an nbsp
<a name="line633"></a>    // to the container, and leave the cursor inside of it
<a name="line634"></a>    if (isInOneContainer) {
<a name="line635"></a>      var container = goog.editor.style.getContainer(range.getStartNode());
<a name="line636"></a>      if (goog.editor.node.isEmpty(container, true)) {
<a name="line637"></a>        var html = &#39;&amp;nbsp;&#39;;
<a name="line638"></a>        if (goog.userAgent.OPERA &amp;&amp;
<a name="line639"></a>            container.tagName == goog.dom.TagName.LI) {
<a name="line640"></a>          // Don&#39;t break Opera&#39;s native break-out-of-lists behavior.
<a name="line641"></a>          html = &#39;&lt;br&gt;&#39;;
<a name="line642"></a>        }
<a name="line643"></a>        container.innerHTML = html;
<a name="line644"></a>        goog.editor.range.selectNodeStart(container.firstChild);
<a name="line645"></a>        reselect = false;
<a name="line646"></a>      }
<a name="line647"></a>    }
<a name="line648"></a>
<a name="line649"></a>    if (isPartialEnd) {
<a name="line650"></a>      /*
<a name="line651"></a>       This code handles the following, where | is the cursor:
<a name="line652"></a>         &lt;div&gt;a|b&lt;/div&gt;&lt;div&gt;c|d&lt;/div&gt;
<a name="line653"></a>       After removeContents, the remaining HTML is
<a name="line654"></a>         &lt;div&gt;a&lt;/div&gt;&lt;div&gt;d&lt;/div&gt;
<a name="line655"></a>       which means the line break between the two divs remains.  This block
<a name="line656"></a>       moves children of the second div in to the first div to get the correct
<a name="line657"></a>       result:
<a name="line658"></a>         &lt;div&gt;ad&lt;/div&gt;
<a name="line659"></a>
<a name="line660"></a>       TODO: Should we wrap the second div&#39;s contents in a span if they
<a name="line661"></a>                     have inline style?
<a name="line662"></a>      */
<a name="line663"></a>      var rangeStart = goog.editor.style.getContainer(range.getStartNode());
<a name="line664"></a>      var redundantContainer = goog.editor.node.getNextSibling(rangeStart);
<a name="line665"></a>      if (redundantContainer) {
<a name="line666"></a>        goog.editor.node.transferChildren(rangeStart, redundantContainer);
<a name="line667"></a>        goog.dom.removeNode(redundantContainer);
<a name="line668"></a>      }
<a name="line669"></a>    }
<a name="line670"></a>
<a name="line671"></a>    if (reselect) {
<a name="line672"></a>      // The contents of the original range are gone, so restore the cursor
<a name="line673"></a>      // position at the start of where the range once was.
<a name="line674"></a>      range = goog.dom.Range.createCaret(nodeOffset.findTargetNode(baseNode),
<a name="line675"></a>          rangeOffset);
<a name="line676"></a>      range.select();
<a name="line677"></a>    }
<a name="line678"></a>  }
<a name="line679"></a>};
<a name="line680"></a>
<a name="line681"></a>
<a name="line682"></a>/**
<a name="line683"></a> * Checks whether the whole range is in a single block-level element.
<a name="line684"></a> * @param {goog.dom.AbstractRange} range The range to check.
<a name="line685"></a> * @return {boolean} Whether the whole range is in a single block-level element.
<a name="line686"></a> * @private
<a name="line687"></a> */
<a name="line688"></a>goog.editor.plugins.EnterHandler.isInOneContainerW3c_ = function(range) {
<a name="line689"></a>  // Find the block element containing the start of the selection.
<a name="line690"></a>  var startContainer = range.getStartNode();
<a name="line691"></a>  if (goog.editor.style.isContainer(startContainer)) {
<a name="line692"></a>    startContainer = startContainer.childNodes[range.getStartOffset()] ||
<a name="line693"></a>                     startContainer;
<a name="line694"></a>  }
<a name="line695"></a>  startContainer = goog.editor.style.getContainer(startContainer);
<a name="line696"></a>
<a name="line697"></a>  // Find the block element containing the end of the selection.
<a name="line698"></a>  var endContainer = range.getEndNode();
<a name="line699"></a>  if (goog.editor.style.isContainer(endContainer)) {
<a name="line700"></a>    endContainer = endContainer.childNodes[range.getEndOffset()] ||
<a name="line701"></a>                   endContainer;
<a name="line702"></a>  }
<a name="line703"></a>  endContainer = goog.editor.style.getContainer(endContainer);
<a name="line704"></a>
<a name="line705"></a>  // Compare the two.
<a name="line706"></a>  return startContainer == endContainer;
<a name="line707"></a>};
<a name="line708"></a>
<a name="line709"></a>
<a name="line710"></a>/**
<a name="line711"></a> * Checks whether the end of the range is not at the end of a block-level
<a name="line712"></a> * element.
<a name="line713"></a> * @param {goog.dom.AbstractRange} range The range to check.
<a name="line714"></a> * @return {boolean} Whether the end of the range is not at the end of a
<a name="line715"></a> *     block-level element.
<a name="line716"></a> * @private
<a name="line717"></a> */
<a name="line718"></a>goog.editor.plugins.EnterHandler.isPartialEndW3c_ = function(range) {
<a name="line719"></a>  var endContainer = range.getEndNode();
<a name="line720"></a>  var endOffset = range.getEndOffset();
<a name="line721"></a>  var node = endContainer;
<a name="line722"></a>  if (goog.editor.style.isContainer(node)) {
<a name="line723"></a>    var child = node.childNodes[endOffset];
<a name="line724"></a>    // Child is null when end offset is &gt;= length, which indicates the entire
<a name="line725"></a>    // container is selected.  Otherwise, we also know the entire container
<a name="line726"></a>    // is selected if the selection ends at a new container.
<a name="line727"></a>    if (!child ||
<a name="line728"></a>        child.nodeType == goog.dom.NodeType.ELEMENT &amp;&amp;
<a name="line729"></a>        goog.editor.style.isContainer(child)) {
<a name="line730"></a>      return false;
<a name="line731"></a>    }
<a name="line732"></a>  }
<a name="line733"></a>
<a name="line734"></a>  var container = goog.editor.style.getContainer(node);
<a name="line735"></a>  while (container != node) {
<a name="line736"></a>    if (goog.editor.node.getNextSibling(node)) {
<a name="line737"></a>      return true;
<a name="line738"></a>    }
<a name="line739"></a>    node = node.parentNode;
<a name="line740"></a>  }
<a name="line741"></a>
<a name="line742"></a>  return endOffset != goog.editor.node.getLength(endContainer);
<a name="line743"></a>};
</pre>


</body>
</html>
